﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;

namespace Oops.Xna.Framework
{
	/// <summary>
	/// An extension to the <see cref="Microsoft.Xna.Framework.Graphics.Model" /> class. 
	/// </summary>
	public static class ModelExtension
	{
		/// <summary>
		/// Extracts the positions of the vertices and the indices of the triangles that make up
		/// the specified <see cref="Microsoft.Xna.Framework.Graphics.Model"/>.
		/// </summary>
		/// <param name="model">The source <see cref="Microsoft.Xna.Framework.Graphics.Model"/>.</param>
		/// <param name="positions">A <see cref="List&lt;Vector3&gt;"/> instance to store the positions of the vertices.</param>
		/// <param name="indices">A <see cref="List&lt;int&gt;"/> instance to store the indices of the triangles.</param>
		public static void Extract(this Model model, List<Vector3> positions, List<int> indices)
		{
			// Get the bones of the model.		
			Matrix[] bones = new Matrix[model.Bones.Count];
			model.CopyAbsoluteBoneTransformsTo(bones);

			// Iterate on the model's meshes.
			for (int i = 0; i < model.Meshes.Count; i++)
			{
				// Get the the current mesh.
				ModelMesh mesh = model.Meshes[i];

				// Get the mesh's associated bone.
				Matrix bone = bones[mesh.ParentBone.Index];
				
				// Iterate on the current mesh's mesh parts.
				for (int j = 0; j < mesh.MeshParts.Count; j++)
				{
					// Get the current mesh part.
					ModelMeshPart meshPart = mesh.MeshParts[j];

					// Hold onto the current position count.
					int count = positions.Count;

					// Get the mesh part's associated vertices.
					Vector3[] vertices = new Vector3[meshPart.NumVertices];
					mesh.VertexBuffer.GetData<Vector3>(
						meshPart.StreamOffset + (meshPart.BaseVertex * meshPart.VertexStride),
						vertices,
						0,
						meshPart.NumVertices,
						meshPart.VertexStride);

					// Transform the vertices to object space.
					Vector3.Transform(vertices, ref bone, vertices);

					// Add the vertices.
					positions.AddRange(vertices);

					// Get the mesh part's associated indices.
					switch (mesh.IndexBuffer.IndexElementSize)
					{
						case IndexElementSize.SixteenBits:
							// Get the mesh part's associated triangle indices.
							ushort[] indices0 = new ushort[meshPart.PrimitiveCount * 3];
							mesh.IndexBuffer.GetData<ushort>(
								meshPart.StartIndex * 2,
								indices0,
								0,
								meshPart.PrimitiveCount * 3);

							// Iterate on the triangles.
							for (int k = 0; k < meshPart.PrimitiveCount; k++)
							{
								// Get the indices into the position collection for the current triangle.
								int index = k * 3;
								ushort index0 = (ushort)(indices0[index + 2] + count);
								ushort index1 = (ushort)(indices0[index + 1] + count);
								ushort index2 = (ushort)(indices0[index + 0] + count);

								// Check for degenerate case.
								if (!(positions[index2] - positions[index1]).IsZeroLength() &&
									!(positions[index1] - positions[index0]).IsZeroLength() &&
									!(positions[index0] - positions[index2]).IsZeroLength())
								{
									// Add the indices for this triangle to the list.
									indices.Add(index0);
									indices.Add(index1);
									indices.Add(index2);
								}
							}

							break;

						case IndexElementSize.ThirtyTwoBits:
							// Get the mesh part's associated triangle indices.
							int[] indices1 = new int[meshPart.PrimitiveCount * 3];
							mesh.IndexBuffer.GetData<int>(
								meshPart.StartIndex,
								indices1,
								0,
								meshPart.PrimitiveCount * 3);

							// Iterate on the triangles.
							for (int k = 0; k < meshPart.PrimitiveCount; k++)
							{
								// Get the indices into the position collection for the current triangle.
								int index = k * 3;
								int index0 = indices1[index + 2] + count;
								int index1 = indices1[index + 1] + count;
								int index2 = indices1[index + 0] + count;

								// Check for degenerate case.
								if (!(positions[index2] - positions[index1]).IsZeroLength() &&
									!(positions[index1] - positions[index0]).IsZeroLength() &&
									!(positions[index0] - positions[index2]).IsZeroLength())
								{
									// Add the indices for this triangle to the list.
									indices.Add(index0);
									indices.Add(index1);
									indices.Add(index2);
								}
							}

							break;
					}
				}
			}
		}
	}
}
